﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;

#if NETFX_CORE || UNIVERSAL
using Windows.Graphics.Imaging;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using Buffer = System.Buffer;
#else
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
#endif
namespace Prometheus.TypeUtility
{
    public static class TypeExpanderExtension
    {
        #region HasProperty

        private static PropertyInfo GetTypeProperty(this Type theType,string propertyName)
        {
            PropertyInfo propertyInfo = theType.
#if !NETFX_CORE && !UNIVERSAL
                GetProperty
#else
GetRuntimeProperty
#endif
(propertyName)
;
            return propertyInfo;
        }

        private static bool SimpleHasProperty(this object obj, string property)
        {
            PropertyInfo propertyInfo = obj.GetType().GetTypeProperty(property);
            return propertyInfo != null;
        }

        public static bool HasProperty(this object obj, string property)
        {
            if (obj == null) return false;
            if (String.IsNullOrEmpty(property)) return true;
            int indexOf = property.IndexOf(".", StringComparison.Ordinal);
            if (indexOf == -1) return obj.SimpleHasProperty(property);
            string firstObject = property.Substring(0, indexOf);
            if (!obj.SimpleHasProperty(firstObject)) return false;
            object fObject = obj.GetSimpleProperty(firstObject);
            string sbstr = property.Substring(indexOf + 1);
            return fObject.SimpleHasProperty(sbstr);
        }

        #endregion

        #region SetProperty

        private static void SetSimpleProperty(this object obj, string property, object value)
        {
            PropertyInfo propertyInfo = obj.GetType().GetTypeProperty(property);
            if (propertyInfo != null)
            {
                propertyInfo.SetValue(obj, value, null);
            }
            else
            {
                throw new InvalidOperationException(String.Format("Свойство {0} не найдено", property));
            }
        }

        public static void SetProperty(this object obj, string property, object value)
        {
            if (!String.IsNullOrEmpty(property))
            {
                int indexOf = property.IndexOf(".", StringComparison.Ordinal);
                if (indexOf == -1) obj.SetSimpleProperty(property, value);
                else
                {
                    string firstObject = property.Substring(0, indexOf);
                    object fObject = obj.GetSimpleProperty(firstObject);
                    string sbstr = property.Substring(indexOf + 1);
                    fObject.SetProperty(sbstr, value);
                }
            }
        }

        #endregion

        #region GetProperty

        private static object GetSimpleProperty(this object obj, string property)
        {
            PropertyInfo propertyInfo = obj.GetType().GetTypeProperty(property);
            if (propertyInfo != null)
            {
                object value = propertyInfo.GetValue(obj, null);
                return value;
            }
            throw new InvalidOperationException(String.Format("Свойство {0} не найдено", property));
        }

        public static TValType GetProperty<TValType>(this object obj, string property)
        {
            return (TValType) obj.GetProperty(property);
        }

        public static object GetPropertyOrNull(this object obj, string propName)
        {
            try
            {
                return obj.GetProperty(propName);
            }
            catch
            {
                return null;
            }
        }

        public static object GetProperty(this object obj, string property)
        {
            if (String.IsNullOrEmpty(property)) return obj;
            int indexOf = property.IndexOf(".", StringComparison.Ordinal);
            if (indexOf == -1) return obj.GetSimpleProperty(property);
            string firstObject = property.Substring(0, indexOf);
            object fObject = obj.GetSimpleProperty(firstObject);
            string sbstr = property.Substring(indexOf + 1);
            return fObject.GetProperty(sbstr);
        }

        #endregion

        public static object CastOrConvert(this object obj, Type toType)
        {
            if (obj == null) return null;

            if (obj.GetType() == toType) return obj;

            //automatically cast bool to visibility
            if ((toType == typeof (Visibility)) && (obj is bool))
            {
                return (bool) obj ? Visibility.Visible : Visibility.Collapsed;
            }
#if !NETFX_CORE && !UNIVERSAL
            //Actually SilverLight does not provide it... but let it be for the future SL versions
            var typeConverterAttubute =
                toType.GetCustomAttributes(typeof (TypeConverterAttribute), true).LastOrDefault() as
                TypeConverterAttribute;
            if (typeConverterAttubute != null)
            {
                Type convType = Type.GetType(typeConverterAttubute.ConverterTypeName);
                Debug.Assert(convType != null, "convType != null");
                ConstructorInfo constructorInfo = convType.GetConstructor(Type.EmptyTypes);

                if (constructorInfo == null)
                    throw new InvalidOperationException(
                        "Trying to convert the value by type converter, which has no default constructor: " + convType);

                var converter = (TypeConverter) constructorInfo.Invoke(new object[0]);
                if (converter.CanConvertFrom(obj.GetType()))
                {
                    return converter.ConvertFrom(obj);
                }
            }
#endif
            if (toType.
#if NETFX_CORE || UNIVERSAL
                GetTypeInfo().
#endif
IsEnum)
            {
                try
                {
                    return Enum.Parse(toType, obj.ToString(), false);
                }
                catch (ArgumentException)
                {
                    throw new SomethingInvalidException();
                }
            }

            if (toType.
#if NETFX_CORE || UNIVERSAL
GetTypeInfo().
#endif
                IsGenericType)
            {
                if (toType.
#if NETFX_CORE || UNIVERSAL
GetTypeInfo().GenericTypeArguments[0].GetTypeInfo()
#else
GetGenericArguments()[0]
#endif
                    .IsEnum)
                {
                    try
                    {
                        return Enum.Parse(toType.
#if NETFX_CORE || UNIVERSAL
GetTypeInfo().GenericTypeArguments[0]
#else
                            GetGenericArguments()[0]
#endif
                            , obj.ToString(), false);
                    }
                    catch (ArgumentException)
                    {
                        throw new SomethingInvalidException();
                    }
                }

                if (toType.GetGenericTypeDefinition() == typeof (Nullable<>))
                {
                    var str = obj as string;
                    if (str != null)
                        if (str == string.Empty)
                        {
                            return null;
                        }
                    toType = toType.
#if NETFX_CORE || UNIVERSAL
GetTypeInfo().GenericTypeArguments[0]
#else
                            GetGenericArguments()[0]
#endif
                            ;
                }
#if !NETFX_CORE && !UNIVERSAL
                var convertible = obj as IConvertible;
                if (convertible != null)
                {
                    return convertible.ToType(toType, null);
                }
#endif
                return obj; //.ToString(); the same as below: why to string ??
            }

            if (toType == typeof (ImageSource) && obj is byte[])
            {
                return ((byte[]) obj).ConvertToImageSource();
            }

            {
#if !NETFX_CORE && !UNIVERSAL
                var convertible = obj as IConvertible;
                if (convertible != null)
                {
                    return convertible.ToType(toType, null);
                }
#endif
                return obj;
            }
        }


        public static ImageSource ConvertToImageSource(this byte[] value)
        {
            //if(!(value is byte[]))throw new InvalidCastException("Необходимо передать byte[]");
            
            
            var bitmap = new BitmapImage();
            var memoryStream = new MemoryStream(value);
#if NETFX_CORE  || UNIVERSAL
            var imageStream = new InMemoryRandomAccessStream();
            Stream asStreamForWrite = imageStream.AsStreamForWrite();
            asStreamForWrite.Write(value,0,value.Length);
            bitmap.SetSource(imageStream);
            imageStream.Dispose();
#else
#if !SILVERLIGHT 
            bitmap.StreamSource = memoryStream;
#else
            bitmap.SetSource(memoryStream);
#endif
#endif
            memoryStream.Dispose();
            return bitmap;
        }

        public static string ToHHMMSS(this TimeSpan ts)
        {
            string mmss = string.Format("{0}:{1}", ts.Minutes,
                                        ts.Seconds.ToString(CultureInfo.InvariantCulture).PadLeft(2, '0'));

            if (ts.Hours > 0)
            {
                mmss = ts.Hours + ":" + mmss;
            }
            return mmss;
        }

        public static T[] ToArray2<T>(this ObservableCollection<T> source)
        {
            return source.ToArray();
        }

        public static ObservableCollection<T> ToObservableCollection<T>(this IEnumerable<T> source)
        {
            var collection = new ObservableCollection<T>();
            foreach (T item in source)
            {
                collection.Add(item);
            }
            return collection;
        }
    }
}